// $Id: CacheManager.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 NourY Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
/*! \file
 * \brief
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */

#if (!NET_CF)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Web;
using System.Web.Caching;
using System.Web.SessionState;
using Salamanca.DataAccess;


namespace Salamanca.DataAccess.Web
{



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Arguments of the <see cref="CacheManagerAdapter.GeneratingStringKey" /> event.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public sealed class GeneratingStringKeyEventArgs:
        EventArgs
    {

        /// <summary>Constructor.</summary>
        public GeneratingStringKeyEventArgs(IPrimaryKey key)
        {
            _Key=key;
            GeneratedKey=string.Format(
                CultureInfo.InvariantCulture,
                "{0}_{1}",
                key.GetType(),
                key.Value
            );
        }

        /// <summary>The original key.</summary>
        public IPrimaryKey Key
        {
            get
            {
                return _Key;
            }
        }

        /// <summary>The generated key.</summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        public string GeneratedKey;

        /// <summary>The original key.</summary>
        private IPrimaryKey _Key;
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Arguments of the <see cref="CacheManagerAdapter.AddingElement" /> event.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public sealed class AddingElementEventArgs:
        EventArgs
    {

        /// <summary>Constructor.</summary>
        public AddingElementEventArgs(string key, IDomainEntity value)
        {
            _Key=key;
            _Value=value;

            AbsoluteExpiration=Cache.NoAbsoluteExpiration;
            SlidingExpiration=Cache.NoSlidingExpiration;
            Priority=CacheItemPriority.Default;
        }

        /// <summary>The key.</summary>
        public string Key
        {
            get
            {
                return _Key;
            }
        }

        /// <summary>The element to be added.</summary>
        public IDomainEntity Value
        {
            get
            {
                return _Value;
            }
        }

        /// <summary>The dependencies.</summary>
        /// <seealso cref="Cache.Add" />
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        public CacheDependency Dependencies;
        /// <summary>The absolute expiration time.</summary>
        /// <seealso cref="Cache.Add" />
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        public DateTime AbsoluteExpiration;
        /// <summary>The sliding expiration time span.</summary>
        /// <seealso cref="Cache.Add" />
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        public TimeSpan SlidingExpiration;
        /// <summary>The priority.</summary>
        /// <seealso cref="Cache.Add" />
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        public CacheItemPriority Priority;
        /// <summary>The callback called when the item is removed from the cache.</summary>
        /// <seealso cref="Cache.Add" />
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        public CacheItemRemovedCallback OnRemoveCallback;

        /// <summary>The key.</summary>
        private string _Key;
        /// <summary>The element to be added.</summary>
        private IDomainEntity _Value;
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Base class to implement a <see cref="ICacheManager">cache manager</see> adapter around a web <see cref="Cache" />.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class CacheManagerAdapter:
        ICacheManager
    {

        /// <summary>Constructor.</summary>
        public CacheManagerAdapter(Cache cache)
        {
            Debug.Assert(cache!=null);
            if (cache==null)
                throw new ArgumentNullException("cache");

            _Cache=cache;
        }

        /// <summary>Raises the <see cref="CacheManagerAdapter.AddingElement" /> event.</summary>
        /// <remarks>
        ///   <para>By default, the element is added to the web <see cref="Cache" /> with the following parameters :
        ///     <list type="bullet">
        ///       <item><description>no <see cref="CacheDependency" /> (<c>null</c>).</description></item>
        ///       <item><description><see cref="M:Cache.NoAbsoluteExpiration" />.</description></item>
        ///       <item><description><see cref="M:Cache.NoSlidingExpiration" />.</description></item>
        ///       <item><description><see cref="CacheItemPriority.Default" />.</description></item>
        ///       <item><description>no <see cref="CacheItemRemovedCallback" /> (<c>null</c>).</description></item>
        ///     </list>
        /// To change this behaviour, you can override this method.
        /// </para>
        /// <include file="../../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        /// <seealso cref="M:Cache.Add" />
        protected virtual void OnAddingElement(AddingElementEventArgs e)
        {
            if (AddingElement!=null)
                AddingElement(this, e);
        }

        /// <summary>Raises the <see cref="CacheManagerAdapter.GeneratingStringKey" /> event.</summary>
        /// <remarks>
        ///   <para>The web <see cref="Cache" /> only accepts string keys. When a string key
        /// is about to be generated for an element, this method is called.</para>
        ///   <para>The string key generated by default is the concatenation of a <include file="../../doc/Global.doc.xml" path="doc/template[@name='DomainEntity']/*" /> key
        /// <see cref="Type" /> name with the string representation of the key <see cref="IPrimaryKey.Value" />.
        /// If distinct <see cref="IPrimaryKey.Value" />s have the same string representation, you must then either :
        ///     <list type="bullet">
        ///       <item><description>override <see cref="Object.ToString()" /> for the <see cref="IPrimaryKey.Value" /> type so that different values have different string representations.</description></item>
        ///       <item><description>override this method to create a new string key.</description></item>
        ///     </list>
        /// </para>
        /// <include file="../../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnGeneratingStringKey(GeneratingStringKeyEventArgs e)
        {
            if (GeneratingStringKey!=null)
                GeneratingStringKey(this, e);
        }

        private string GetStringKey(IPrimaryKey key)
        {
            GeneratingStringKeyEventArgs args=new GeneratingStringKeyEventArgs(key);
            OnGeneratingStringKey(args);

            return args.GeneratedKey;
        }

        private void Add(string key, IDomainEntity value)
        {
            AddingElementEventArgs args=new AddingElementEventArgs(key, value);
            OnAddingElement(args);

            Cache.Add(key, value, args.Dependencies, args.AbsoluteExpiration, args.SlidingExpiration, args.Priority, args.OnRemoveCallback);
        }

        /// <summary>Adds an element with the provided key and value to the <see cref="CacheManagerAdapter" />.</summary>
        void ICacheManager.Add(object key, object value)
        {
            Add(GetStringKey((IPrimaryKey)key), (IDomainEntity)value);
        }

        /// <summary>Removes all keys and values from the <see cref="CacheManagerAdapter" />.</summary>
        void ICacheManager.Clear()
        {
            List<string> rem=new List<string>(_Cache.Count);
            foreach (IDictionaryEnumerator e in _Cache)
                if (e.Value is IDomainEntity)
                    rem.Add((string)e.Key);
            foreach (string k in rem)
                _Cache.Remove(k);
        }

        /// <summary>Determines whether the <see cref="CacheManagerAdapter" /> contains an element with the specified key.</summary>
        bool ICacheManager.ContainsKey(object key)
        {
            object v;
            return ((ICacheManager)this).TryGetValue(key, out v);
        }

        /// <summary>Removes the element with the specified key from the <see cref="CacheManagerAdapter" />.</summary>
        bool ICacheManager.Remove(object key)
        {
            return _Cache.Remove(GetStringKey((IPrimaryKey)key))!=null;
        }

        /// <summary>Gets the value associated with the specified key.</summary>
        bool ICacheManager.TryGetValue(object key, out object value)
        {
            object v=_Cache[GetStringKey((IPrimaryKey)key)];
            if (v is IDomainEntity)
                value=(IDomainEntity)v;
            else
                value=null;

            return value!=null;
        }

        /// <summary>Gets or sets the element with the specified key.</summary>
        object ICacheManager.this[object key]
        {
            get
            {
                return _Cache[GetStringKey((IPrimaryKey)key)];
            }
        }

        /// <summary>The adapted web <see cref="Cache" />.</summary>
        protected Cache Cache
        {
            get
            {
                return _Cache;
            }
        }

        /// <summary>Event raised when a new element is about to be added to the cache.</summary>
        public event EventHandler<AddingElementEventArgs> AddingElement;
        /// <summary>Event raised when a new string key is about to be generated for an element.</summary>
        public event EventHandler<GeneratingStringKeyEventArgs> GeneratingStringKey;

        /// <summary>The adapted web <see cref="Cache" />.</summary>
        private Cache _Cache;

    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Base class to implement a <see cref="ICacheManager">cache manager</see> adapter around a web <see cref="HttpSessionState" />.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class SessionCacheManager:
        ICacheManager
    {

        /// <summary>Constructor.</summary>
        public SessionCacheManager(HttpSessionState session)
        {
            Debug.Assert(session!=null);
            if (session==null)
                throw new ArgumentNullException("session");

            _Session=session;
        }

        /// <summary>Raises the <see cref="CacheManagerAdapter.GeneratingStringKey" /> event.</summary>
        /// <remarks>
        ///   <para>The web <see cref="Cache" /> only accepts string keys. When a string key
        /// is about to be generated for an element, this method is called.</para>
        ///   <para>The string key generated by default is the concatenation of a <include file="../../doc/Global.doc.xml" path="doc/template[@name='DomainEntity']/*" /> key
        /// <see cref="Type" /> name with the string representation of the key <see cref="IPrimaryKey.Value" />.
        /// If distinct <see cref="IPrimaryKey.Value" />s have the same string representation, you must then either :
        ///     <list type="bullet">
        ///       <item><description>override <see cref="Object.ToString()" /> for the <see cref="IPrimaryKey.Value" /> type so that different values have different string representations.</description></item>
        ///       <item><description>override this method to create a new string key.</description></item>
        ///     </list>
        /// </para>
        /// <include file="../../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnGeneratingStringKey(GeneratingStringKeyEventArgs e)
        {
            if (GeneratingStringKey!=null)
                GeneratingStringKey(this, e);
        }

        private string GetStringKey(IPrimaryKey key)
        {
            GeneratingStringKeyEventArgs args=new GeneratingStringKeyEventArgs(key);
            OnGeneratingStringKey(args);

            return args.GeneratedKey;
        }

        private void Add(string key, IDomainEntity value)
        {
            Session.Add(key, value);
        }

        /// <summary>Adds an element with the provided key and value to the <see cref="SessionCacheManager" />.</summary>
        void ICacheManager.Add(object key, object value)
        {
            Add(GetStringKey((IPrimaryKey)key), (IDomainEntity)value);
        }

        /// <summary>Removes all keys and values from the <see cref="SessionCacheManager" />.</summary>
        void ICacheManager.Clear()
        {
            List<string> rem=new List<string>(_Session.Count);
            foreach (IDictionaryEnumerator e in _Session)
                if (e.Value is IDomainEntity)
                    rem.Add((string)e.Key);
            foreach (string k in rem)
                _Session.Remove(k);
        }

        /// <summary>Determines whether the <see cref="SessionCacheManager" /> contains an element with the specified key.</summary>
        bool ICacheManager.ContainsKey(object key)
        {
            object v;
            return ((ICacheManager)this).TryGetValue(key, out v);
        }

        /// <summary>Removes the element with the specified key from the <see cref="SessionCacheManager" />.</summary>
        bool ICacheManager.Remove(object key)
        {
            _Session.Remove(GetStringKey((IPrimaryKey)key));
            return true;
        }

        /// <summary>Gets the value associated with the specified key.</summary>
        bool ICacheManager.TryGetValue(object key, out object value)
        {
            object v=_Session[GetStringKey((IPrimaryKey)key)];
            if (v is IDomainEntity)
                value=(IDomainEntity)v;
            else
                value=null;

            return value!=null;
        }

        /// <summary>Gets or sets the element with the specified key.</summary>
        object ICacheManager.this[object key]
        {
            get
            {
                return _Session[GetStringKey((IPrimaryKey)key)];
            }
        }

        /// <summary>The adapted web <see cref="HttpSessionState" />.</summary>
        protected HttpSessionState Session
        {
            get
            {
                return _Session;
            }
        }

        /// <summary>Event raised when a new string key is about to be generated for an element.</summary>
        public event EventHandler<GeneratingStringKeyEventArgs> GeneratingStringKey;

        /// <summary>The adapted web <see cref="HttpSessionState" />.</summary>
        private HttpSessionState _Session;

    }
}
#endif
